using System;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;

namespace Meta {
	[Serializable]
	public class RemoteDataPlaceholder {
		public RemoteDataPlaceholder(object o) {
			PrintedRepresentation = Writer.SafeWriteToString(o,100);
		}
		public readonly string PrintedRepresentation;
		public override string ToString() {
			return PrintedRepresentation;
		}

		public static object[] PlaceholderArray(object[] a) {
			if (a==null)
				return a;
			return PlaceholderArray(a, a.Length);
		}

		public static object[] PlaceholderArray(object[] a, int length) {
			if (a==null)
				return a;
			object[] result = new object[length];
			for (int i=0;i<length; i++) {
				object o = a[i];
				if (o is System.Reflection.Missing || Utilities.IsRemote(o))
					result[i] = new RemoteDataPlaceholder(o);
				else if (o==null || o is Expression || o is Expression[] || o is Symbol || o is Exception || o is Procedure || Type.GetTypeCode(o.GetType())!=TypeCode.Object)
					// Easy to dump so just dump it.
					result[i] = o;
				else
					result[i] = new RemoteDataPlaceholder(o);
			}
			return result;
		}
	}

	public class MissingObjectSurrogate : ISerializationSurrogate {
		public static void Install(BinaryFormatter f) {
			if (f.SurrogateSelector==null)
				f.SurrogateSelector = new SurrogateSelector();
			((SurrogateSelector)(f.SurrogateSelector)).AddSurrogate(typeof(System.Reflection.Missing), new StreamingContext(StreamingContextStates.All), new MissingObjectSurrogate());
		}
			
		void ISerializationSurrogate.GetObjectData(object o, SerializationInfo info, StreamingContext context) {
			info.SetType(typeof(MissingRef));
		}

		object ISerializationSurrogate.SetObjectData(object o, SerializationInfo info, StreamingContext c, ISurrogateSelector sel) {
			throw new Exception("SetObjectData method called on MissingObjectSurrogate, which shouldn't happen");
		}

	}

	[Serializable]
	public class MissingRef : IObjectReference {
		object IObjectReference.GetRealObject(StreamingContext context) {
			return System.Reflection.Missing.Value;
		}
	}

	[Serializable]
	public class ProcedurePlaceholder : UserProcedure, ISerializable, IArglist {
		public override object Apply(object[] args) {
			throw new NotImplementedException("Can't call a placeholder procedure");
		}

		[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
		void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
			info.AddValue("name", name);
			info.AddValue("arglist", arglist);
		}

		object[] IArglist.Arglist {
			get {
				return arglist;
			}
		}

		object[] arglist;

		protected ProcedurePlaceholder(SerializationInfo info, StreamingContext context) {
			this.name = info.GetValue("name", typeof(object));
			this.arglist = (object[])info.GetValue("arglist", typeof(object[]));
		}
		public ProcedurePlaceholder(object name, object[] arglist) {
			this.name = name;
			this.arglist = arglist;
		}
	}


	public class FileToken {
		string path;

		public string Path {
			get {
				return path;
			}
			set {
				// Remove old entry
				if (path!=null)
					tokenTable[path.ToLower()] = null;

				// Make new entry
				path = value;
				if (path!=null)
					tokenTable[path.ToLower()] = this;
			}
		}

		static Hashtable tokenTable = new Hashtable();

		// For per-file fluids
		public readonly Hashtable fluidTable = new Hashtable();

		public static FileToken GetToken(string path) {
			try {
				path = new FileInfo(path).FullName;
			} catch {
				// Just in case path doesn't actually exist
			}
			FileToken probe = (path==null)?null:tokenTable[path.ToLower()] as FileToken;

			if (probe==null) {
				FileToken t = new FileToken(path);
				return t;
			} else
				return probe;
		}

		FileToken(string path) {
			this.Path = path;
		}

		public FileToken() {
			path = null;
		}

		public override string ToString() {
			if (path==null)
				return "unsaved window";
			else
				return path;
		}

	}



	[Serializable]
	public class Escape : CallInterfaceProcedure {
		public class Exception : System.Exception, ISerializable {
			public Exception() {}
			protected Exception(SerializationInfo info, StreamingContext context) : base(info, context) {}
		}
		public object returnValue;
		public Exception exception = new Exception();

		public override object Call(object arg) {
			returnValue = arg;
			throw exception;
		}
	}

	public class TypeMaker {
		static ModuleBuilder module;

		static TypeMaker() {
			AppDomain ad = System.Threading.Thread.GetDomain();
			AssemblyName an = new AssemblyName();
			an.Name = "MetaDynamicCode";
			AssemblyBuilder ab = ad.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
			module = ab.DefineDynamicModule("MetaDynamicCode");
		}
		
		public static Type MakeType(Symbol name, IList fields) {
			TypeBuilder tb = module.DefineType(name.name);
			
			foreach (Symbol fieldName in fields)
				tb.DefineField(fieldName.name, typeof(object), FieldAttributes.Public);

			return tb.CreateType();
		}
	}

	public class Installation {
		public static string HelpFilePath(string name) {
			return helpDirectory + Path.DirectorySeparatorChar + name;
		}

		public static string HelpFilePath(string type, string name) {
			return helpDirectory + Path.DirectorySeparatorChar + type + Path.DirectorySeparatorChar + name + ".htm";
		}

		public static string installDirectory;
		public static string helpDirectory;
		static string productName = "Meta";
		static Installation() {
			installDirectory = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
			while (installDirectory != "" && installDirectory != null && Path.GetFileName(installDirectory) != productName)
				installDirectory = Path.GetDirectoryName(installDirectory);
			if (installDirectory == null || installDirectory == "") {
				installDirectory = "C:\\Program Files\\Northwestern University\\Meta\\";
				if (!Directory.Exists(installDirectory))
					MessageBox.Show("Please make sure the system is installed in a directory called "+productName+".  Some features won't work otherwise.",
						"Can't find installation directory");
			}
			helpDirectory = installDirectory + Path.DirectorySeparatorChar + "Help";
		}
	}

	// Alas, this has to go here because the macro processor isn't smart enough to compile it.
	public class InNamespaceParser : Parser, IArglist {
		public override object IndentStyle {
			get {
				return Symbol.sBody;
			}
		}

		object[] Arglist {
			get {
				return new object[] { "namespace", "expression", "..." };
			}
		}

		public override Expression Parse(object expression, Parser subparser) {
			IList list = expression as IList;
			if (list.Count<3)
				throw new SyntaxError("No expressions specified after namespace argument", expression);
			GlobalVariable nse = subparser.Parse(list[1], subparser) as GlobalVariable;
			if (nse==null)
				throw new SyntaxError("First argument must be a namespace", list[1]);
			NamespaceBase ns = nse.value as NamespaceBase;
			if (ns==null)
				throw new SyntaxError("First argument must be a namespace", list[1]);

			object body = Lists.Cons(Symbol.sBegin, Lists.Rest(Lists.Rest(list)));

			return ns.Parse(body, ns);
		}
	}

	[Serializable]
	public class Values {
		public object[] values;
		public Values(object[] values) {
			this.values = values;
		}

		static Values() {
			Writer.SetDisclosure(typeof(Values), new string[] { "values" });
		}
	}

	public class IComparerWrapper : System.Collections.IComparer {
		public readonly Procedure procedure;
		public readonly EvalStack estack;

		public IComparerWrapper(Procedure proc) {
			procedure = proc;
			estack = EvalStack.CurrentThreadEvalStack;
		}

		int System.Collections.IComparer.Compare(object x, object y) {
			object result = Procedure.TracedCall(procedure, estack, x, y);
			if (!(result is int))
				throw new TypeException("Comparison procedures must return an Int32", result, typeof(int));
			return (int)result;
		}
	}

	public interface IArglist {
		object[] Arglist { get; }
	}

	public interface IDocumentable {
		string Documentation { get; }
	}
}